/*************************************************************************
 *************************************************************************
 	 	 	 	 	 	 M2SMA : Scheduler.cpp
 *************************************************************************
 *
 	 Copyright (C) 2008
 	 Steven Costiou, Julien Yven; Master 2 Complex Systems
 	 University of Western Brittany (UBO), Brest, France
 *
 *************************************************************************
 *************************************************************************
 *
 	 Copyright (C) 2011
 	 M2SMA reworked and simplified core, Steven Costiou, France
 *
 **************************************************************************
 **************************************************************************
 *
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/
 *
 **************************************************************************
 **************************************************************************/

#include "Scheduler.h"
#include <stdlib.h>
#include <sstream>

Scheduler::Scheduler()
{
	initializeAttributes();
}

Scheduler::~Scheduler()
{
	map<string, set<Agent*> >::iterator mapIt;

	eraseAllAgents();

	for (mapIt = this->agentListByClasses->begin(); mapIt != this->agentListByClasses->end(); mapIt++)
		mapIt->second.clear();

	this->agentPtrs->clear();
	this->toScheduleAgents->clear();
	this->agentList->clear();
	this->agentClassesList->clear();
	this->agentListByClasses->clear();

	delete this->agentPtrs;
	delete this->toScheduleAgents;
	delete this->agentList;
	delete this->agentClassesList;
	delete this->agentListByClasses;
}

Scheduler::Scheduler(const Scheduler& aScheduler)
{
}

void Scheduler::initializeAttributes()
{
	this->schedulingMode = SEQUENTIAL_SCHEDULING;
	agentCounter = 0;
	srand(time(0));

	this->agentPtrs = new set<Agent*>();
	this->agentList = new map<string, Agent*>();
	this->toScheduleAgents= new vector<Agent*>();
	this->agentClassesList= new map<string, int>();
	this->agentListByClasses= new map<string, set<Agent*> >();
}

string Scheduler::intToString(int anInteger)
{
	stringstream aStream;
	aStream << anInteger;

	return aStream.str().c_str();
}

/**
 *  Builds a name for an agent using its class name and the agentNumber list.
 */
string Scheduler::buildName(string anAgentClassName)
{
	string aNewName;

	increaseNumberOfCreatedAgentOfClass(anAgentClassName);
	int absoluteAgentClassCounter = getNumberOfCreatedAgentOfClass(anAgentClassName);

	aNewName = anAgentClassName + "_" + intToString(absoluteAgentClassCounter);

	return aNewName;
}

void Scheduler::increaseNumberOfCreatedAgentOfClass(string anAgentClassName)
{
	this->agentClassesList->find(anAgentClassName)->second += 1;
}

/**
 * Returns the absolute number of agents created for a given class.
 */
int Scheduler::getNumberOfCreatedAgentOfClass(string anAgentClassName)
{
	if (this->agentClassesList->find(anAgentClassName) == this->agentClassesList->end())
	{
		this->agentClassesList->insert(pair<string, int>(anAgentClassName, 0));
		return 0;
	}
	else
	{
		return this->agentClassesList->find(anAgentClassName)->second;
	}
}

/**
 * Initializes a new agent and adds it to the agent list.
 */
void Scheduler::newAgent(Agent* anAgent)
{
	string aNewName;
	string aClassName;

	aClassName = anAgent->getClassName();
	aNewName = buildName(aClassName);

	anAgent->setName(aNewName);

	this->addAgent(anAgent);
}

/**
 * Adds an agent to the agents data structures.
 */
void Scheduler::addAgent(Agent* anAgent)
{
	increaseAgentCounter();

	agentList->insert(pair<string, Agent*> (anAgent->getName(), anAgent));
	this->agentPtrs->insert(anAgent);
	addNewAgentToClassOrderedLists(anAgent);
}

/**
 * Add a new agent into the class-ordered agent list.
 * For a given class name, the list of agents of this class
 * are stored in an associated set.
 */
void Scheduler::addNewAgentToClassOrderedLists(Agent* anAgent)
{
	vector<string>* anAgentClassNames = anAgent->getClassNames();
	unsigned int anAgentClassNamesSize = anAgentClassNames->size();

	for(unsigned int i = 0; i < anAgentClassNamesSize; ++i)
	{
		string aClassName = anAgentClassNames->at(i);

		if (this->agentListByClasses->find(aClassName) == this->agentListByClasses->end())
		{
			set<Agent *> s;
			s.insert(anAgent);
			agentListByClasses->insert(pair<string, set<Agent*> > (aClassName, s));
		}
		else
		{
			(this->agentListByClasses->find(aClassName)->second).insert(anAgent);
		}
	}
}

/**
 * Warning : to be used with caution.
 * Except for specific cases (for example in inherited classes),
 * pointers shall not be manually added to the agent pointer set.
 */
void Scheduler::addAgentPointer(Agent* anAgentPointer)
{
	if(this->agentPtrs != NULL)
		this->agentPtrs->insert(anAgentPointer);

	cout << "Agent pointer manually added to the pointer set : " << anAgentPointer->getName();
}

/**
 *  Remove an agent from all data structures, must be called by an Agent
 */
void Scheduler::removeAndClearAgent(Agent* anAgent)
{
	this->agentList->erase(anAgent->getName());
	this->agentListByClasses->find(anAgent->getClassName())->second.erase(anAgent);
	this->agentCounter--;
}

void Scheduler::eraseAllAgents()
{
	set<Agent*>::iterator it;

	/**
	 * Agents pointers are the exact same object for every data structures.
	 * It is needed to destroy the pointed object one and only one time.
	 */
	for (it = this->agentPtrs->begin(); it != this->agentPtrs->end(); it++)
		if((*it) != NULL)
			delete *it;
}

/**
 * Does a scheduling cycle, according to the current mode (see m_mode)
 */
void Scheduler::cycle()
{
	switch(this->schedulingMode)
	{
		case SEQUENTIAL_SCHEDULING:
			performSequentialScheduling();
			break;

		case RANDOM_SCHEDULING:
			performRandomScheduling();
			break;

		default:
			throw new SchedulingModeException(this->schedulingMode);
			break;
	}
}

void Scheduler::performSequentialScheduling()
{
	map<string, Agent *>::iterator it;
	for (it = this->agentList->begin(); it != this->agentList->end(); it++)
		(*it).second->live();
}

void Scheduler::performRandomScheduling()
{
	map<string, Agent *>::iterator it;
	for (it = this->agentList->begin(); it != this->agentList->end(); it++)
	{
		this->toScheduleAgents->push_back((*it).second);
	}

	int randomIndex;
	unsigned int toScheduleAgentSize;

	while(this->toScheduleAgents->size() > 0)
	{
		toScheduleAgentSize = this->toScheduleAgents->size();
		randomIndex = static_cast<int>((toScheduleAgentSize*rand() / (RAND_MAX)));

		this->toScheduleAgents->at(randomIndex)->live();
		this->toScheduleAgents->erase(this->toScheduleAgents->begin() + randomIndex);
	}
}
